/****************************************************************************************/
/* MediaFrame is an Open Source streaming media platform in Java                        */
/* which provides a fast, easy to implement and extremely small applet                  */
/* that enables to view your audio/video content without having                         */
/* to rely on external player applications or bulky plug-ins.                           */
/*                                                                                      */
/*--------------------------------------------------------------------------------------*/
/*                                                                                      */
/* "VideoFrame.java"                                                                    */
/*                                                                                      */
/*--------------------------------------------------------------------------------------*/
/*                                                                                      */
/* We changed a lot of code and added a lot of functionality.							*/
/* This includes, but not limited to, the following changes:							*/
/* 1. The project was renamed to MediaFrame;											*/
/* 2. The connection speed detection procedure was added;								*/
/* 3. The JavaScript API functions were added;											*/
/* 4. The pre and post image support functionality was added;							*/
/* 5. The ability to save movie into the local disk was added;							*/
/* 6. The inner buffer for a movie file was added;										*/
/* 7. The click-through functionality was added;    									*/
/* 8. The .zip files support was added;    												*/
/* 9. The realtime feedback agent functionality was added.    							*/
/* For the full list of the current functionality please visit the following web page:	*/
/* http://mediaframe.org/																*/
/*    																					*/
/* 06 Jul 2002 - 19 Dec 2004 Konstantin Belous, Oleg Lebedev							*/
/*																						*/
/*--------------------------------------------------------------------------------------*/
/* This file represents one I/P/B-frame, an Sequence-header or an GOP-start. It is able */
/* to parse this information from an InputStream and is used by the CutBuffer to        */
/* buffer video. If this element encounters an audio-sample it will make an             */
/* AudioElement and give this to the CutBuffer.                                         */
/*                                                                                      */
/* -- Ron -- (29-04-2002) r_breukelaar@hotmail.com                                      */
/*                                                                                      */
/*--------------------------------------------------------------------------------------*/
/*                                                                                      */
/* This program is free software; you can redistribute it and/or modify it under the    */
/* terms of the GNU General Public License as published by the Free Software            */
/* Foundation; either version 2 of the License, or (at your option) any later version.  */
/*                                                                                      */
/* This program is distributed in the hope that it will be useful, but WITHOUT ANY      */
/* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      */
/* PARTICULAR PURPOSE. See the GNU General Public License for more details.             */
/*                                                                                      */
/* You should have received a copy of the GNU General Public License along with this    */
/* program; (See "LICENSE.GPL"). If not, write to the Free Software Foundation, Inc.,   */
/* 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.                            */
/*                                                                                      */
/*--------------------------------------------------------------------------------------*/
/*                                                                                      */
/* If the program runs as Java applet it isn't "interactive" in the sense of the GNU    */
/* General Public License. So paragraph 2c doesn't apply.                               */
/*                                                                                      */
/****************************************************************************************/

package mediaframe.mpeg1;

import java.io.*;

/**
 * The <code>VideoFrame</code> class represents one I/P/B-frame, an Sequence-header or an GOP-start.
 * It extends the <code>DataFrame</code> class and contains methods for reading of the video packet.
 */
public class VideoFrame extends DataFrame
{
	/** The last 4 bytes in the input stream. */
	private long _long;
	/** The input stream with movie's data. */
	private InputStream _is;

	/** The buffer for audio data. */
	private DataBuffer audioBuffer = null;
	/** The buffer for video data. */
	private DataBuffer videoBuffer  = null;

	/**
	 * Constructs a <code>VideoFrame</code> object using the specified time stamp value 
	 * and buffers for audio and video data.
	 * @param time the time stamp value of the frame.
	 * @param videoBuffer the buffer for video data.
	 * @param audioBuffer the buffer for audio data.
	 */
	public VideoFrame(long time, DataBuffer videoBuffer, DataBuffer audioBuffer) {

		super(time, videoBuffer.getBufferPosition());
		
		this.audioBuffer = audioBuffer;
		this.videoBuffer = videoBuffer;
	}

	/**
 	 * This routine reads video data into the buffer from the input stream  
 	 * to byte which equals 1 or until the end of the file will be reached. 
 	 * Calls the routines that read or skip the blocks of movie's data.
 	 * @param is the input stream with movie's data.
 	 * @param next32 the last 4 bytes from the input stream.
 	 * @return the last 4 bytes in the stream.
 	 * @throws Exception if an error occurs.
 	 */ 
	public long fill(InputStream is, long next32) throws Exception
	{
		_long = next32;
		_is = is;

		//while (_type == B_FRAME || _type == -1 || _type == P_FRAME)
		{
			// goto next PIC, SEQ or GOP
			while (_long != 0x100 && _long != 0x1b3 && _long != 0x1b8 && !_eof)
				skip_to_1();

			if (_long == 0x100)
			{
				read(); read();
				_type = (int)((_long >> 3) & 7);
				//if (_type == B_FRAME)
				//	skip();
			}
			else if (_long == 0x1b3)
				_type = SEQ_START;
			else if (_long == 0x1b8)
				_type = GOP_START;
		}

		// not PIC, SEQ or GOP
		do
		{
			if (_long == 0x1e0)
				skip_1e0();
			else if (_long == 0x1c0)
				skip_1c0();
			else if (_long == 0x1ba)
				skip_1ba();
			else if (_long == 0x1bb)
				skip_1bb();
			else if (_long == 0x1be)
				skip_1be();
			else if (_long == 0x1b9)
				skip_1b9();
			else if (_long == 0x1b2) // USER_START
				skip_1b2();
			else
				read_to_1();

			Thread.yield();
		}
                while (_long != 0x100 && _long != 0x1b3 && _long != 0x1b8 && !_eof);

		if (_eof)
			_long = -1;

		return _long;
	}

	/**
	 * This routine skips the data from the input stream to byte which equals 0x1e0.
 	 * @throws Exception if an error occurs.
	 */ 
	public void skip_1e0() throws Exception
	{
		skip(); skip();

		skip();
		// skip stuffing
		while ((_long & 0xff) == 0xff)
			skip();

		// skip 01XXXXXX XXXXXXXX
		if (((_long >> 6) & 3) == 1)
		{
			skip();
			skip();
		}

		// skip 0010TTTX TTTTTTTT TTTTTTTX TTTTTTTT TTTTTTTX
		if (((_long >> 4) & 15) == 2)
		{
                        _time_stamp = ((_long >> 1) & 7);
                        skip(); _time_stamp = ((_time_stamp << 8) | (_long & 255));
                        skip(); _time_stamp = ((_time_stamp << 7) | ((_long >> 1) & 127));
                        skip(); _time_stamp = ((_time_stamp << 8) | (_long & 255));
                        skip(); _time_stamp = ((_time_stamp << 7) | ((_long >> 1) & 127));
                        skip();

		}

		// skip 0011TTTX TTTTTTTT TTTTTTTX TTTTTTTT TTTTTTTX
		//      XXXXPPPX PPPPPPPP PPPPPPPX PPPPPPPP PPPPPPPX
		else if (((_long >> 4) & 15) == 3)
		{
                        _time_stamp = (_long & 14);
                        skip(); _time_stamp = (_time_stamp << 7) + (_long & 255);
			skip(); _time_stamp = (_time_stamp << 7) + ((_long >> 1) & 127);
			skip(); _time_stamp = (_time_stamp << 8) + (_long & 255);
			skip(); _time_stamp = (_time_stamp << 7) + ((_long >> 1) & 127);
                        skip(); skip();
			skip(); skip();
			skip(); skip();
		}

		// skip one byte
		else
			skip();

		skip(); skip(); skip();
	}

	/**
	 * This routine skips the data from the input stream to byte which equals 0x1c0.
	 * After that it creates audio frame and starts filling this frame with the data. 
 	 * @throws Exception if an error occurs.
	 */ 
	public void skip_1c0() throws Exception
	{
		skip(); skip();

		skip();
		// skip stuffing
		while ((_long & 0xff) == 0xff)
			skip();

		// skip 01XXXXXX XXXXXXXX
		if (((_long >> 6) & 3) == 1)
		{
			skip();
			skip();
		}

		// skip 0010TTTX TTTTTTTT TTTTTTTX TTTTTTTT TTTTTTTX
		if (((_long >> 4) & 15) == 2)
		{
                        _time_stamp = ((_long >> 1) & 7);
                        skip(); _time_stamp = ((_time_stamp << 8) | (_long & 255));
                        skip(); _time_stamp = ((_time_stamp << 7) | ((_long >> 1) & 127));
                        skip(); _time_stamp = ((_time_stamp << 8) | (_long & 255));
                        skip(); _time_stamp = ((_time_stamp << 7) | ((_long >> 1) & 127));
                        skip();

		}

		// skip 0011TTTX TTTTTTTT TTTTTTTX TTTTTTTT TTTTTTTX
		//      XXXXPPPX PPPPPPPP PPPPPPPX PPPPPPPP PPPPPPPX
		else if (((_long >> 4) & 15) == 3)
		{
                        _time_stamp = (_long & 14);
                        skip(); _time_stamp = (_time_stamp << 7) + (_long & 255);
			skip(); _time_stamp = (_time_stamp << 7) + ((_long >> 1) & 127);
			skip(); _time_stamp = (_time_stamp << 8) + (_long & 255);
			skip(); _time_stamp = (_time_stamp << 7) + ((_long >> 1) & 127);
                        skip(); skip();
			skip(); skip();
			skip(); skip();
		}

		// skip one byte
		else
			skip();

		skip(); skip(); skip();

		AudioFrame af = new AudioFrame(_time_stamp, audioBuffer);

		audioBuffer.registerNewFrame(af);

		_long = af.fill(_is, _long);

		if (_long != -1)
			audioBuffer.saveCurrentFrame();
		else {
			audioBuffer.saveLastFrame();
			_eof = true;
		}
	}

	/**
	 * This routine skips the data from the input stream to byte which equals 0x1ba.
 	 * @throws Exception if an error occurs.
	 */ 
	public void skip_1ba() throws Exception
	{
		skip();

		while (_long < 0x100 || _long > 0x1ff)
			skip_to_1();
	}

	/**
	 * This routine skips the data from the input stream to byte which equals 0x1bb.
 	 * @throws Exception if an error occurs.
	 */ 
	public void skip_1bb() throws Exception
	{
		skip();
		while (_long < 0x100 || _long > 0x1ff)
			skip_to_1();
	}

	/**
	 * This routine skips the data from the input stream to byte which equals 0x1be.
 	 * @throws Exception if an error occurs.
	 */ 
	public void skip_1be() throws Exception
	{
		skip();
		while (_long < 0x100 || _long > 0x1ff)
			skip_to_1();
	}

	/**
	 * This routine skips the data from the input stream to byte which equals 0x1b9.
 	 * @throws Exception if an error occurs.
	 */ 
	public void skip_1b9() throws Exception
	{
		skip();
		while (_long < 0x100 || _long > 0x1ff)
			skip_to_1();
	}

	/**
	 * This routine skips the data from the input stream to byte which equals 0x1b2.
 	 * @throws Exception if an error occurs.
	 */ 
	public void skip_1b2() throws Exception
	{
		skip();
		while (_long < 0x100 || _long > 0x1ff)
			skip_to_1();
	}

	/**
	 * This routine reads one byte of the data from the input stream.
 	 * @throws Exception if an error occurs.
	 */ 
	private void read() throws Exception
	{
		int r = _is.read();

		if (r != -1)
		{
			videoBuffer.write((char)((_long >>> 24) & 255));

			_long = ((_long << 8) & MASK) | r;

		}
		else
			_eof = true;
	}

	/**
	 * Skips one byte from the input stream.
 	 * @throws Exception if an error occurs.
	 */ 
	private void skip() throws Exception
	{
		int r = _is.read();

		if (r != -1)
			_long = ((_long << 8) & MASK) | r;
		else {	
//			System.out.println("EOF Detected!");
			_eof = true;			
		}
	}

	/**
	 * This routine reads the data from the input stream to byte which equals 1.
 	 * @throws Exception if an error occurs.
	 */ 
	private void read_to_1() throws Exception
	{
		int r;
		do
		{
			r = _is.read();

			if (r != -1)
			{

				videoBuffer.write((char)((_long >>> 24) & 255));

				_long = ((_long << 8) & MASK) | r;
			}
			else
				_eof = true;
		}
		while ((_long >>> 8) != 1 && !_eof);
	}

	/**
	 * This routine skips the data from the input stream to byte which equals 1.
 	 * @throws Exception if an error occurs.
	 */ 
	private void skip_to_1() throws Exception
	{
		int r = 0;
		do
		{
			r = _is.read();
			if (r != -1)
				_long = ((_long << 8) & MASK) | r;
			else{
				_eof = true;
			}
		}
		while ((_long >>> 8) != 1 && !_eof);
	}
}
